// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/spdy/spdy_alt_svc_wire_format.h"

#include "base/logging.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/platform_test.h"

using base::StringPiece;
using ::testing::_;

namespace net {

namespace test {

    // Expose all private methods of class SpdyAltSvcWireFormat.
    class SpdyAltSvcWireFormatPeer {
    public:
        static void SkipWhiteSpace(StringPiece::const_iterator* c,
            StringPiece::const_iterator end)
        {
            SpdyAltSvcWireFormat::SkipWhiteSpace(c, end);
        }
        static bool PercentDecode(StringPiece::const_iterator c,
            StringPiece::const_iterator end,
            std::string* output)
        {
            return SpdyAltSvcWireFormat::PercentDecode(c, end, output);
        }
        static bool ParseAltAuthority(StringPiece::const_iterator c,
            StringPiece::const_iterator end,
            std::string* host,
            uint16_t* port)
        {
            return SpdyAltSvcWireFormat::ParseAltAuthority(c, end, host, port);
        }
        static bool ParsePositiveInteger16(StringPiece::const_iterator c,
            StringPiece::const_iterator end,
            uint16_t* max_age)
        {
            return SpdyAltSvcWireFormat::ParsePositiveInteger16(c, end, max_age);
        }
        static bool ParsePositiveInteger32(StringPiece::const_iterator c,
            StringPiece::const_iterator end,
            uint32_t* max_age)
        {
            return SpdyAltSvcWireFormat::ParsePositiveInteger32(c, end, max_age);
        }
    };

} // namespace test

namespace {

    // Generate header field values, possibly with multiply defined parameters and
    // random case, and corresponding AlternativeService entries.
    void FuzzHeaderFieldValue(
        int i,
        std::string* header_field_value,
        SpdyAltSvcWireFormat::AlternativeService* expected_altsvc)
    {
        if (!header_field_value->empty()) {
            header_field_value->push_back(',');
        }
        expected_altsvc->protocol_id = "a=b%c";
        header_field_value->append("a%3Db%25c=\"");
        expected_altsvc->host = "";
        if (i & 1 << 0) {
            expected_altsvc->host = "foo\"bar\\baz";
            header_field_value->append("foo\\\"bar\\\\baz");
        }
        expected_altsvc->port = 42;
        header_field_value->append(":42\"");
        if (i & 1 << 1) {
            header_field_value->append(" ");
        }
        if (i & 3 << 2) {
            expected_altsvc->max_age = 1111;
            header_field_value->append(";");
            if (i & 1 << 2) {
                header_field_value->append(" ");
            }
            header_field_value->append("mA=1111");
            if (i & 2 << 2) {
                header_field_value->append(" ");
            }
        }
        if (i & 1 << 4) {
            header_field_value->append("; J=s");
        }
        if (i & 1 << 5) {
            expected_altsvc->version.push_back(24);
            header_field_value->append("; v=\"24\"");
        }
        if (i & 1 << 6) {
            expected_altsvc->max_age = 999999999;
            header_field_value->append("; Ma=999999999");
        }
        if (i & 1 << 7) {
            header_field_value->append(";");
        }
        if (i & 1 << 8) {
            header_field_value->append(" ");
        }
        if (i & 1 << 9) {
            header_field_value->append(",");
        }
        if (i & 1 << 10) {
            header_field_value->append(" ");
        }
    }

    // Generate AlternativeService entries and corresponding header field values in
    // canonical form, that is, what SerializeHeaderFieldValue() should output.
    void FuzzAlternativeService(int i,
        SpdyAltSvcWireFormat::AlternativeService* altsvc,
        std::string* expected_header_field_value)
    {
        if (!expected_header_field_value->empty()) {
            expected_header_field_value->push_back(',');
        }
        altsvc->protocol_id = "a=b%c";
        altsvc->port = 42;
        expected_header_field_value->append("a%3Db%25c=\"");
        if (i & 1 << 0) {
            altsvc->host = "foo\"bar\\baz";
            expected_header_field_value->append("foo\\\"bar\\\\baz");
        }
        expected_header_field_value->append(":42\"");
        if (i & 1 << 1) {
            altsvc->max_age = 1111;
            expected_header_field_value->append("; ma=1111");
        }
        if (i & 1 << 2) {
            altsvc->version.push_back(24);
            altsvc->version.push_back(25);
            expected_header_field_value->append("; v=\"24,25\"");
        }
    }

    class SpdyAltSvcWireFormatTest : public ::testing::Test {
    };

    // Tests of public API.

    TEST(SpdyAltSvcWireFormatTest, DefaultValues)
    {
        SpdyAltSvcWireFormat::AlternativeService altsvc;
        EXPECT_EQ("", altsvc.protocol_id);
        EXPECT_EQ("", altsvc.host);
        EXPECT_EQ(0u, altsvc.port);
        EXPECT_EQ(86400u, altsvc.max_age);
        EXPECT_TRUE(altsvc.version.empty());
    }

    TEST(SpdyAltSvcWireFormatTest, ParseInvalidEmptyHeaderFieldValue)
    {
        SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
        ASSERT_FALSE(SpdyAltSvcWireFormat::ParseHeaderFieldValue("", &altsvc_vector));
    }

    TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValueClear)
    {
        SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
        ASSERT_TRUE(
            SpdyAltSvcWireFormat::ParseHeaderFieldValue("clear", &altsvc_vector));
        EXPECT_EQ(0u, altsvc_vector.size());
    }

    // Fuzz test of ParseHeaderFieldValue() with optional whitespaces, ignored
    // parameters, duplicate parameters, trailing space, trailing alternate service
    // separator, etc.  Single alternative service at a time.
    TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValue)
    {
        for (int i = 0; i < 1 << 11; ++i) {
            std::string header_field_value;
            SpdyAltSvcWireFormat::AlternativeService expected_altsvc;
            FuzzHeaderFieldValue(i, &header_field_value, &expected_altsvc);
            SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
            ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(header_field_value,
                &altsvc_vector));
            ASSERT_EQ(1u, altsvc_vector.size());
            EXPECT_EQ(expected_altsvc.protocol_id, altsvc_vector[0].protocol_id);
            EXPECT_EQ(expected_altsvc.host, altsvc_vector[0].host);
            EXPECT_EQ(expected_altsvc.port, altsvc_vector[0].port);
            EXPECT_EQ(expected_altsvc.max_age, altsvc_vector[0].max_age);
            EXPECT_EQ(expected_altsvc.version, altsvc_vector[0].version);

            // Roundtrip test starting with |altsvc_vector|.
            std::string reserialized_header_field_value = SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector);
            SpdyAltSvcWireFormat::AlternativeServiceVector roundtrip_altsvc_vector;
            ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
                reserialized_header_field_value, &roundtrip_altsvc_vector));
            ASSERT_EQ(1u, roundtrip_altsvc_vector.size());
            EXPECT_EQ(expected_altsvc.protocol_id,
                roundtrip_altsvc_vector[0].protocol_id);
            EXPECT_EQ(expected_altsvc.host, roundtrip_altsvc_vector[0].host);
            EXPECT_EQ(expected_altsvc.port, roundtrip_altsvc_vector[0].port);
            EXPECT_EQ(expected_altsvc.max_age, roundtrip_altsvc_vector[0].max_age);
            EXPECT_EQ(expected_altsvc.version, roundtrip_altsvc_vector[0].version);
        }
    }

    // Fuzz test of ParseHeaderFieldValue() with optional whitespaces, ignored
    // parameters, duplicate parameters, trailing space, trailing alternate service
    // separator, etc.  Possibly multiple alternative service at a time.
    TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValueMultiple)
    {
        for (int i = 0; i < 1 << 11;) {
            std::string header_field_value;
            SpdyAltSvcWireFormat::AlternativeServiceVector expected_altsvc_vector;
            // This will generate almost two hundred header field values with two,
            // three, four, five, six, and seven alternative services each, and
            // thousands with a single one.
            do {
                SpdyAltSvcWireFormat::AlternativeService expected_altsvc;
                FuzzHeaderFieldValue(i, &header_field_value, &expected_altsvc);
                expected_altsvc_vector.push_back(expected_altsvc);
                ++i;
            } while ((i < 1 << 13) && (i % 6 < i % 7));
            SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
            ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(header_field_value,
                &altsvc_vector));
            ASSERT_EQ(expected_altsvc_vector.size(), altsvc_vector.size());
            for (unsigned int j = 0; j < altsvc_vector.size(); ++j) {
                EXPECT_EQ(expected_altsvc_vector[j].protocol_id,
                    altsvc_vector[j].protocol_id);
                EXPECT_EQ(expected_altsvc_vector[j].host, altsvc_vector[j].host);
                EXPECT_EQ(expected_altsvc_vector[j].port, altsvc_vector[j].port);
                EXPECT_EQ(expected_altsvc_vector[j].max_age, altsvc_vector[j].max_age);
                EXPECT_EQ(expected_altsvc_vector[j].version, altsvc_vector[j].version);
            }

            // Roundtrip test starting with |altsvc_vector|.
            std::string reserialized_header_field_value = SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector);
            SpdyAltSvcWireFormat::AlternativeServiceVector roundtrip_altsvc_vector;
            ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
                reserialized_header_field_value, &roundtrip_altsvc_vector));
            ASSERT_EQ(expected_altsvc_vector.size(), roundtrip_altsvc_vector.size());
            for (unsigned int j = 0; j < roundtrip_altsvc_vector.size(); ++j) {
                EXPECT_EQ(expected_altsvc_vector[j].protocol_id,
                    roundtrip_altsvc_vector[j].protocol_id);
                EXPECT_EQ(expected_altsvc_vector[j].host,
                    roundtrip_altsvc_vector[j].host);
                EXPECT_EQ(expected_altsvc_vector[j].port,
                    roundtrip_altsvc_vector[j].port);
                EXPECT_EQ(expected_altsvc_vector[j].max_age,
                    roundtrip_altsvc_vector[j].max_age);
                EXPECT_EQ(expected_altsvc_vector[j].version,
                    roundtrip_altsvc_vector[j].version);
            }
        }
    }

    TEST(SpdyAltSvcWireFormatTest, SerializeEmptyHeaderFieldValue)
    {
        SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
        EXPECT_EQ("clear",
            SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector));
    }

    // Test ParseHeaderFieldValue() and SerializeHeaderFieldValue() on the same pair
    // of |expected_header_field_value| and |altsvc|, with and without hostname and
    // each
    // parameter.  Single alternative service at a time.
    TEST(SpdyAltSvcWireFormatTest, RoundTrip)
    {
        for (int i = 0; i < 1 << 3; ++i) {
            SpdyAltSvcWireFormat::AlternativeService altsvc;
            std::string expected_header_field_value;
            FuzzAlternativeService(i, &altsvc, &expected_header_field_value);

            // Test ParseHeaderFieldValue().
            SpdyAltSvcWireFormat::AlternativeServiceVector parsed_altsvc_vector;
            ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
                expected_header_field_value, &parsed_altsvc_vector));
            ASSERT_EQ(1u, parsed_altsvc_vector.size());
            EXPECT_EQ(altsvc.protocol_id, parsed_altsvc_vector[0].protocol_id);
            EXPECT_EQ(altsvc.host, parsed_altsvc_vector[0].host);
            EXPECT_EQ(altsvc.port, parsed_altsvc_vector[0].port);
            EXPECT_EQ(altsvc.max_age, parsed_altsvc_vector[0].max_age);
            EXPECT_EQ(altsvc.version, parsed_altsvc_vector[0].version);

            // Test SerializeHeaderFieldValue().
            SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
            altsvc_vector.push_back(altsvc);
            EXPECT_EQ(expected_header_field_value,
                SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector));
        }
    }

    // Test ParseHeaderFieldValue() and SerializeHeaderFieldValue() on the same pair
    // of |expected_header_field_value| and |altsvc|, with and without hostname and
    // each
    // parameter.  Multiple alternative services at a time.
    TEST(SpdyAltSvcWireFormatTest, RoundTripMultiple)
    {
        SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
        std::string expected_header_field_value;
        for (int i = 0; i < 1 << 3; ++i) {
            SpdyAltSvcWireFormat::AlternativeService altsvc;
            FuzzAlternativeService(i, &altsvc, &expected_header_field_value);
            altsvc_vector.push_back(altsvc);
        }

        // Test ParseHeaderFieldValue().
        SpdyAltSvcWireFormat::AlternativeServiceVector parsed_altsvc_vector;
        ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
            expected_header_field_value, &parsed_altsvc_vector));
        ASSERT_EQ(altsvc_vector.size(), parsed_altsvc_vector.size());
        SpdyAltSvcWireFormat::AlternativeServiceVector::iterator expected_it = altsvc_vector.begin();
        SpdyAltSvcWireFormat::AlternativeServiceVector::iterator parsed_it = parsed_altsvc_vector.begin();
        for (; expected_it != altsvc_vector.end(); ++expected_it, ++parsed_it) {
            EXPECT_EQ(expected_it->protocol_id, parsed_it->protocol_id);
            EXPECT_EQ(expected_it->host, parsed_it->host);
            EXPECT_EQ(expected_it->port, parsed_it->port);
            EXPECT_EQ(expected_it->max_age, parsed_it->max_age);
            EXPECT_EQ(expected_it->version, parsed_it->version);
        }

        // Test SerializeHeaderFieldValue().
        EXPECT_EQ(expected_header_field_value,
            SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector));
    }

    // ParseHeaderFieldValue() should return false on malformed field values:
    // invalid percent encoding, unmatched quotation mark, empty port, non-numeric
    // characters in numeric fields.
    TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValueInvalid)
    {
        SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
        const char* invalid_field_value_array[] = { "a%",
            "a%x",
            "a%b",
            "a%9z",
            "a=",
            "a=\"",
            "a=\"b\"",
            "a=\":\"",
            "a=\"c:\"",
            "a=\"c:foo\"",
            "a=\"c:42foo\"",
            "a=\"b:42\"bar",
            "a=\"b:42\" ; m",
            "a=\"b:42\" ; min-age",
            "a=\"b:42\" ; ma",
            "a=\"b:42\" ; ma=",
            "a=\"b:42\" ; v=\"..\"",
            "a=\"b:42\" ; ma=ma",
            "a=\"b:42\" ; ma=123bar",
            "a=\"b:42\" ; v=24",
            "a=\"b:42\" ; v=24,25",
            "a=\"b:42\" ; v=\"-3\"",
            "a=\"b:42\" ; v=\"1.2\"",
            "a=\"b:42\" ; v=\"24,\"" };
        for (const char* invalid_field_value : invalid_field_value_array) {
            EXPECT_FALSE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
                invalid_field_value, &altsvc_vector))
                << invalid_field_value;
        }
    }

    // ParseHeaderFieldValue() should return false on a field values truncated
    // before closing quotation mark, without trying to access memory beyond the end
    // of the input.
    TEST(SpdyAltSvcWireFormatTest, ParseTruncatedHeaderFieldValue)
    {
        SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
        const char* field_value_array[] = { "a=\":137\"", "a=\"foo:137\"",
            "a%25=\"foo\\\"bar\\\\baz:137\"" };
        for (const std::string& field_value : field_value_array) {
            for (size_t len = 1; len < field_value.size(); ++len) {
                EXPECT_FALSE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
                    field_value.substr(0, len), &altsvc_vector))
                    << len;
            }
        }
    }

    // Tests of private methods.

    // Test SkipWhiteSpace().
    TEST(SpdyAltSvcWireFormatTest, SkipWhiteSpace)
    {
        StringPiece input("a \tb  ");
        StringPiece::const_iterator c = input.begin();
        test::SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end());
        ASSERT_EQ(input.begin(), c);
        ++c;
        test::SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end());
        ASSERT_EQ(input.begin() + 3, c);
        ++c;
        test::SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end());
        ASSERT_EQ(input.end(), c);
    }

    // Test PercentDecode() on valid input.
    TEST(SpdyAltSvcWireFormatTest, PercentDecodeValid)
    {
        StringPiece input("");
        std::string output;
        ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::PercentDecode(
            input.begin(), input.end(), &output));
        EXPECT_EQ("", output);

        input = StringPiece("foo");
        output.clear();
        ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::PercentDecode(
            input.begin(), input.end(), &output));
        EXPECT_EQ("foo", output);

        input = StringPiece("%2ca%5Cb");
        output.clear();
        ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::PercentDecode(
            input.begin(), input.end(), &output));
        EXPECT_EQ(",a\\b", output);
    }

    // Test PercentDecode() on invalid input.
    TEST(SpdyAltSvcWireFormatTest, PercentDecodeInvalid)
    {
        const char* invalid_input_array[] = { "a%", "a%x", "a%b", "%J22", "%9z" };
        for (const char* invalid_input : invalid_input_array) {
            StringPiece input(invalid_input);
            std::string output;
            EXPECT_FALSE(test::SpdyAltSvcWireFormatPeer::PercentDecode(
                input.begin(), input.end(), &output))
                << input;
        }
    }

    // Test ParseAltAuthority() on valid input.
    TEST(SpdyAltSvcWireFormatTest, ParseAltAuthorityValid)
    {
        StringPiece input(":42");
        std::string host;
        uint16_t port;
        ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority(
            input.begin(), input.end(), &host, &port));
        EXPECT_TRUE(host.empty());
        EXPECT_EQ(42, port);

        input = StringPiece("foo:137");
        ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority(
            input.begin(), input.end(), &host, &port));
        EXPECT_EQ("foo", host);
        EXPECT_EQ(137, port);
    }

    // Test ParseAltAuthority() on invalid input: empty string, no port, zero port,
    // non-digit characters following port.
    TEST(SpdyAltSvcWireFormatTest, ParseAltAuthorityInvalid)
    {
        const char* invalid_input_array[] = { "", ":", "foo:", ":bar",
            ":0", "foo:0", ":12bar", "foo:23bar",
            " ", ":12 ", "foo:12 " };
        for (const char* invalid_input : invalid_input_array) {
            StringPiece input(invalid_input);
            std::string host;
            uint16_t port;
            EXPECT_FALSE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority(
                input.begin(), input.end(), &host, &port))
                << input;
        }
    }

    // Test ParseInteger() on valid input.
    TEST(SpdyAltSvcWireFormatTest, ParseIntegerValid)
    {
        StringPiece input("3");
        uint16_t value;
        ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
            input.begin(), input.end(), &value));
        EXPECT_EQ(3, value);

        input = StringPiece("1337");
        ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
            input.begin(), input.end(), &value));
        EXPECT_EQ(1337, value);
    }

    // Test ParseIntegerValid() on invalid input: empty, zero, non-numeric, trailing
    // non-numeric characters.
    TEST(SpdyAltSvcWireFormatTest, ParseIntegerInvalid)
    {
        const char* invalid_input_array[] = { "", " ", "a", "0", "00", "1 ", "12b" };
        for (const char* invalid_input : invalid_input_array) {
            StringPiece input(invalid_input);
            uint16_t value;
            EXPECT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
                input.begin(), input.end(), &value))
                << input;
        }
    }

    // Test ParseIntegerValid() around overflow limit.
    TEST(SpdyAltSvcWireFormatTest, ParseIntegerOverflow)
    {
        // Largest possible uint16_t value.
        StringPiece input("65535");
        uint16_t value16;
        ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
            input.begin(), input.end(), &value16));
        EXPECT_EQ(65535, value16);

        // Overflow uint16_t, ParsePositiveInteger16() should return false.
        input = StringPiece("65536");
        ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
            input.begin(), input.end(), &value16));

        // However, even if overflow is not checked for, 65536 overflows to 0, which
        // returns false anyway.  Check for a larger number which overflows to 1.
        input = StringPiece("65537");
        ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
            input.begin(), input.end(), &value16));

        // Largest possible uint32_t value.
        input = StringPiece("4294967295");
        uint32_t value32;
        ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger32(
            input.begin(), input.end(), &value32));
        EXPECT_EQ(4294967295, value32);

        // Overflow uint32_t, ParsePositiveInteger32() should return false.
        input = StringPiece("4294967296");
        ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger32(
            input.begin(), input.end(), &value32));

        // However, even if overflow is not checked for, 4294967296 overflows to 0,
        // which returns false anyway.  Check for a larger number which overflows to
        // 1.
        input = StringPiece("4294967297");
        ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger32(
            input.begin(), input.end(), &value32));
    }

} // namespace

} // namespace net
